#pragma once

#include <math.h>

class IEnvelope
{
public:
	double mX1;
	double mX2;
	double mY1;
	double mY2;

	virtual void SetX1(double x1) = 0;
	virtual void SetX2(double x2) = 0;
	virtual void SetY1(double y1) = 0;
	virtual void SetY2(double y2) = 0;

	virtual void Process(const double& position, double& value) = 0;
};

class LogEnvelope : public IEnvelope
{
public:
	virtual void SetX1(double x1) { mX1 = x1; }
	virtual void SetX2(double x2) { mX2 = x2; }
	virtual void SetY1(double y1) { mY1 = y1; }
	virtual void SetY2(double y2) { mY2 = y2; }

	double Log;

	void Process(const double& position, double& value)
	{
		double internalPosition = 1.0 / (mX2 - mX1) * (position - mX1);
		double log = Log;

		if (log > 0)
		{
			internalPosition = log * tanh(internalPosition * 5) + (1 - log) * internalPosition;
		}
		else if (log < 0)
		{
			internalPosition = (1 - log) * internalPosition + (-log) * tanh(-internalPosition * 5);
		}

		value *= mY1 + (mY2 - mY1) * internalPosition;
	}
};

class LeapCurveEnvelope : public IEnvelope
{
private:
	double mLeap;
	double mXX1;
	double ptrX;
	double ptrY;

	void Update()
	{
		double centerX = mX1 + (mX2 - mX1) * 0.5;
		double centerY = mY1 + (mY2 - mY1) * 0.5;
		ptrX = centerX + (centerX - mX1) * mLeap;
		ptrY = centerY + (centerY - mY2) * mLeap;
		mXX1 = 1.0 / (mX2 - mX1);
		if (isinf(mXX1) || isnan(mXX1))
		{
			mXX1 = 0;
		}
	}

public:
	void SetLeap(double leap) { mLeap = leap; Update(); }
	double GetLeap() { return mLeap; }

	virtual void SetX1(double x1) { mX1 = x1; Update(); }
	virtual void SetX2(double x2) { mX2 = x2; Update(); }
	virtual void SetY1(double y1) { mY1 = y1; Update(); }
	virtual void SetY2(double y2) { mY2 = y2; Update(); }

	void Process(const double& position, double& value)
	{
		double internalPosition = mXX1 * (position - mX1);

		double out1_x;
		double out1_y;
		double out2_x;
		double out2_y;
		DoLeap(mX1, mY1, ptrX, ptrY, out1_x, out1_y, internalPosition);
		DoLeap(ptrX, ptrY, mX2, mY2, out2_x, out2_y, internalPosition);
		DoLeap(out1_x, out1_y, out2_x, out2_y, out2_x, out2_y, internalPosition);

		value *= out2_y;
	}

	void DoLeap(double ax, double ay, double bx, double by, double& rX, double& rY, double percent)
	{
		rX = ax + (bx - ax) * percent;
		rY = ay + (by - ay) * percent;
	}
};

class LinearEnvelope : public IEnvelope
{
private:
	double mYY;
	double mXX1;

	void Update()
	{
		mYY = mY2 - mY1;
		mXX1 = 1.0 / mX2 - mX1;
		if (isinf(mXX1) || isnan(mXX1))
		{
			mXX1 = 0;
		}
	}

public:
	virtual void SetX1(double x1) { mX1 = x1; Update(); }
	virtual void SetX2(double x2) { mX2 = x2; Update(); }
	virtual void SetY1(double y1) { mY1 = y1; Update(); }
	virtual void SetY2(double y2) { mY2 = y2; Update(); }

	void Process(const double& position, double& value)
	{
		value *= mY1 + mYY * (mXX1 * (position - mX1));
	}
};